'''
Description: 
Author: Zilu
Date: 2021-11-07 17:39:04
Version: 
LastEditTime: 2021-11-07 19:40:56
LastEditors: Zilu
'''

# 可以使用 __xxx__ 一些特殊变量定义我们需要的定制类
# __str__ 定义了将类转换成字符串时的行为
# __repr__ 定义了直接调用类变量时的行为
class Student(object):
    def __init__(self, name) -> None:
        super().__init__()
        self.__name = name

    def __str__(self):
        return 'Student class (name: %s)' % self.__name

    __repr__ = __str__

s = Student('Micheal')
print(s)
s

# __iter__ 定义类作为可迭代对象时的行为，该方法返回一个可迭代对象，
# 在对该类进行迭代时，使用类中 __next__ 函数定义的行为，直到遇到StopIteration错误
class Fib(object):
    def __init__(self) -> None:
        super().__init__()
        self.a, self.b = 0, 1

    def __iter__(self):
        return self         # 实例就是可迭代对象本身，故返回自身

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > 10000:
            raise StopIteration()
        return self.a

for n in Fib():
    print(n)

# 定义了 __iter__ 的类，在作为可迭代对象时，其特性类似于list，但是却不能用下标索引值
# Fib()[5]        # 使用错误

# 使用下标的行为需要使用 __getitem__ 定义
class FibItem(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a

FibItem()[5]

# 但是list的[]中，除了用下标作索引外，还能使用切片，上面这种 __getitem__ 定义的行为则不能包含slice
class FibItemSlice(object):
    def __getitem__(self, n):
        if (isinstance(n,int)):
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        # slice语法：
        # class slice(stop)
        # class slice(start, stop[, step])
        # 下面没有对 step 和负数做处理，为了简化
        elif (isinstance(n,slice)):
            start = n.start
            stop = n.stop
            if start == None:
                start = 0
            L = []
            a, b = 1, 1
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L

f = FibItemSlice()
f[1]
f[:5]

# 此外，如果把对象看成dict，__getitem__()的参数也可能是一个可以作key的object，例如str。
# 还有__setitem__()方法，把对象视作list或dict来对集合赋值。
# 最后，还有一个__delitem__()方法，用于删除某个元素。
# 这些定制的方法都得益于python的 “鸭子类型”，不需要强制继承某个接口

# 可以使用__getattr__方法，定义使用类中不存在的属性或者方法时的行为，
# 这是因为python在调用不存在的属性时，解释器会尝试使用__getattr__(self,attr)来获得属性
class Classmates(object):
    def __init__(self) -> None:
        super().__init__()
        self.name = 'Micheal'

    def __getattr__(self,attr):
        if not hasattr(Classmates,attr):
            if callable(attr):
                return lambda: 25
            else:
                return 99

c = Classmates()
c.name
c.score
# c.age()       #这种方式出错，因为实际上传入__getattr__的类型是str，判断不出是否callable

# 这种使用实际上可以把一个类的所有属性和方法调用全部动态化处理了，不需要任何特殊手段。
# 现在很多网站都搞REST API，比如新浪微博、豆瓣啥的，调用API的URL类似：
# http://api.server/user/friends
# http://api.server/user/timeline/list
# 如果要写SDK，给每个URL对应的API都写一个方法，那得累死，而且，API一旦改动，SDK也要改。
# 可以写一个Chain，实现链式调用
class Chain(object):
    def __init__(self, path='') -> None:
        super().__init__()
        self._path = path

    def __str__(self):
        return self._path

    def __getattr__(self, path):
        return Chain("%s/%s" %(self._path, path))

    __repr__ = __str__

Chain().status.user.timeline.list
# 无论API怎么变，SDK都可以根据URL实现完全动态的调用，而且不随API的增加而改变

# GitHub 的 REST API会把参数放到URL中，如：
# GET /users/:user/repos，:users 需要替换成实际的用户名
# Chain().users('michael').repos    # TODO

# 调用对象实例方法时，使用instance.method()调用，是使用 __call__ 可以在实例本身上调用
class Student(object):
    def __init__(self, name) -> None:
        super().__init__()
        self.name = name

    def __call__(self):
        print('My name is %s' % self.name)

s = Student('Michael')
s()

# __call__ 就是规定了对象被调用时的行为，或者就是规定了一个对象是否能被调用
# 可以通过 callable() 函数判断是否可以调用
# TODO--DONE
class Chain(object):
    def __init__(self, path='') -> None:
        super().__init__()
        self._path = path

    def __str__(self):
        return self._path

    def __getattr__(self, path):
        return Chain("%s/%s" %(self._path, path))

    def __call__(self,userName):
        return Chain('%s/%s' %(self._path, userName))

    __repr__ = __str__

Chain().users('michael').repos

# 更多可定制的方法，可以见python的官方文档：http://docs.python.org/3/reference/datamodel.html#special-method-names           




